Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/sim/app/api/files/serve/[...path]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ async function compileDocumentIfNeeded(
return { buffer: stored.buffer, contentType: stored.contentType }
}

if (isE2BDocEnabled && getE2BDocFormat(filename)) {
if (isE2BDocEnabled && (await getE2BDocFormat(filename))) {
// Artifact not built yet (still generating, or the source didn't compile at
// write time). Signal "not ready" without compiling — handled as 409.
throw new DocCompileUserError('Document is still being generated')
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export const GET = withRouteHandler(
// In the E2B regime ALL four formats compile in the doc sandbox (Node for
// pptx/docx, Python for pdf/xlsx). Gate on the flag (not the stored MIME) so
// a stale file can't trigger an E2B compile when the sandbox is disabled.
const e2bFmt = isE2BDocEnabled ? getE2BDocFormat(fileRecord.name) : null
const e2bFmt = isE2BDocEnabled ? await getE2BDocFormat(fileRecord.name) : null
const taskId = BINARY_DOC_TASKS[ext]
const isMermaidFile = ext === 'mmd' || ext === 'mermaid'
if (!e2bFmt && !taskId && !isMermaidFile) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,9 @@ import {
DropdownMenuTrigger,
Plus,
} from '@/components/emcn'
import { isWorkflowColumnsEnabledClient } from '@/lib/core/config/env-flags'
import type { ColumnDefinition } from '@/lib/table'
import { COLUMN_TYPE_OPTIONS } from '../column-config-sidebar'

const VISIBLE_COLUMN_TYPE_OPTIONS = isWorkflowColumnsEnabledClient
? COLUMN_TYPE_OPTIONS
: COLUMN_TYPE_OPTIONS.filter((o) => o.type !== 'workflow')

const CELL_HEADER =
'border-[var(--border)] border-r border-b bg-[var(--bg)] px-2 py-[7px] text-left align-middle'

Expand All @@ -32,6 +27,7 @@ interface NewColumnDropdownProps {
onPickType: (type: ColumnDefinition['type']) => void
onPickWorkflow: () => void
onPickEnrichment: () => void
workflowColumnsEnabled: boolean
}

/**
Expand All @@ -45,7 +41,12 @@ export function NewColumnDropdown({
onPickType,
onPickWorkflow,
onPickEnrichment,
workflowColumnsEnabled,
}: NewColumnDropdownProps) {
const visibleColumnTypeOptions = workflowColumnsEnabled
? COLUMN_TYPE_OPTIONS
: COLUMN_TYPE_OPTIONS.filter((o) => o.type !== 'workflow')

const menu = (
<DropdownMenu>
<DropdownMenuTrigger asChild>
Expand All @@ -67,7 +68,7 @@ export function NewColumnDropdown({
)}
</DropdownMenuTrigger>
<DropdownMenuContent align='start' side='bottom' sideOffset={4}>
{isWorkflowColumnsEnabledClient && (
{workflowColumnsEnabled && (
<>
<DropdownMenuItem onSelect={onPickEnrichment}>
<Sparkles className='size-[14px] text-[var(--text-icon)]' />
Expand All @@ -76,7 +77,7 @@ export function NewColumnDropdown({
<DropdownMenuSeparator />
</>
)}
{VISIBLE_COLUMN_TYPE_OPTIONS.map((option) => {
Comment thread
TheodoreSpeaks marked this conversation as resolved.
{visibleColumnTypeOptions.map((option) => {
const Icon = option.icon
const onSelect =
option.type === 'workflow'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,7 @@ interface TableGridProps {
pushTableRenameUndoSinkRef: React.MutableRefObject<
((previousName: string, newName: string) => void) | null
>
workflowColumnsEnabled: boolean
}

/** Serialize a cell value to its tab-separated clipboard representation. */
Expand Down Expand Up @@ -300,6 +301,7 @@ export function TableGrid({
afterDeleteAllSinkRef,
confirmDeleteColumnsSinkRef,
pushTableRenameUndoSinkRef,
workflowColumnsEnabled,
}: TableGridProps) {
const params = useParams()
const workspaceId = propWorkspaceId || (params.workspaceId as string)
Expand Down Expand Up @@ -3738,6 +3740,7 @@ export function TableGrid({
onPickType={handleAddColumnOfType}
onPickWorkflow={handleAddWorkflowColumn}
onPickEnrichment={onOpenEnrichments}
workflowColumnsEnabled={workflowColumnsEnabled}
/>
)}
</tr>
Expand Down
11 changes: 9 additions & 2 deletions apps/sim/app/workspace/[workspaceId]/tables/[tableId]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
import type { Metadata } from 'next'
import { headers } from 'next/headers'
import { getSession } from '@/lib/auth'
import { isFeatureEnabled } from '@/lib/core/config/feature-flags'
import { Table } from './table'

export const metadata: Metadata = {
title: 'Table',
}

export default function TablePage() {
return <Table />
export default async function TablePage() {
const session = await getSession(await headers())
const workflowColumnsEnabled = await isFeatureEnabled('workflow-columns', {
userId: session?.user?.id,
})
return <Table workflowColumnsEnabled={workflowColumnsEnabled} />
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,8 @@ interface TableProps {
/** Identifiers — only set in embedded mode. Page mode reads from `useParams()`. */
workspaceId?: string
tableId?: string
/** Resolved server-side from the workflow-columns feature flag. */
workflowColumnsEnabled?: boolean
}

/**
Expand Down Expand Up @@ -116,6 +118,7 @@ export function Table({
embedded,
workspaceId: propWorkspaceId,
tableId: propTableId,
workflowColumnsEnabled = false,
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
}: TableProps = {}) {
const params = useParams()
const router = useRouter()
Expand Down Expand Up @@ -558,6 +561,7 @@ export function Table({
onPickType={handleAddColumnOfType}
onPickWorkflow={handleAddWorkflowColumn}
onPickEnrichment={onOpenEnrichments}
workflowColumnsEnabled={workflowColumnsEnabled}
/>
) : null

Expand Down Expand Up @@ -691,6 +695,7 @@ export function Table({
afterDeleteAllSinkRef={afterDeleteAllSinkRef}
confirmDeleteColumnsSinkRef={confirmDeleteColumnsSinkRef}
pushTableRenameUndoSinkRef={pushTableRenameUndoSinkRef}
workflowColumnsEnabled={workflowColumnsEnabled}
/>
{userPermissions.canEdit && (
<TableActionBar
Expand Down
9 changes: 5 additions & 4 deletions apps/sim/lib/copilot/tools/handlers/function-execute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { createLogger } from '@sim/logger'
import { decodeVfsPathSegments, encodeVfsPathSegments } from '@/lib/copilot/vfs/path-utils'
import { resolveWorkflowAliasForWorkspace } from '@/lib/copilot/vfs/workflow-alias-resolver'
import { isPlanAliasPath, workflowAliasSandboxPath } from '@/lib/copilot/vfs/workflow-aliases'
import { isMothershipBetaFeaturesEnabled } from '@/lib/core/config/env-flags'
import { isFeatureEnabled } from '@/lib/core/config/feature-flags'
import { queryRows } from '@/lib/table/rows/service'
import { getTableById, listTables } from '@/lib/table/service'
import { listWorkspaceFileFolders } from '@/lib/uploads/contexts/workspace/workspace-file-folder-manager'
Expand Down Expand Up @@ -71,10 +71,11 @@ async function resolveInputFiles(
): Promise<SandboxFile[]> {
const sandboxFiles: SandboxFile[] = []
let totalSize = 0
const betaEnabled = await isFeatureEnabled('mothership-beta')

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 mothership-beta evaluated without user context in several callers

isFeatureEnabled('mothership-beta') is called here (and in workflow-alias-resolver.ts and getE2BDocFormat in doc-compile.ts) without a userId. If the flag is later configured with a userIds allowlist in AppConfig, beta-enabled users would be denied access via these code paths even though WorkspaceVFS.materialize would correctly grant it. The per-user semantics promised in WorkspaceVFS are silently absent anywhere the call chain doesn't have a userId available — worth documenting or passing through as a future hardening item.

Comment thread
cursor[bot] marked this conversation as resolved.

if (inputFiles?.length && workspaceId) {
const allFiles = await listWorkspaceFiles(workspaceId, {
includeReservedSystemFiles: isMothershipBetaFeaturesEnabled,
includeReservedSystemFiles: betaEnabled,
})
for (const fileRef of inputFiles) {
const filePath =
Expand Down Expand Up @@ -136,11 +137,11 @@ async function resolveInputFiles(

if (inputDirectories?.length && workspaceId) {
const folders = await listWorkspaceFileFolders(workspaceId, {
includeReservedSystemFolders: isMothershipBetaFeaturesEnabled,
includeReservedSystemFolders: betaEnabled,
})
const allFiles = await listWorkspaceFiles(workspaceId, {
folders,
includeReservedSystemFiles: isMothershipBetaFeaturesEnabled,
includeReservedSystemFiles: betaEnabled,
})
for (const dirRef of inputDirectories) {
const dirPath =
Expand Down
12 changes: 6 additions & 6 deletions apps/sim/lib/copilot/tools/server/files/doc-compile.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createLogger } from '@sim/logger'
import { isMothershipBetaFeaturesEnabled } from '@/lib/core/config/env-flags'
import { isFeatureEnabled } from '@/lib/core/config/feature-flags'
import { executeInE2B, executeShellInE2B, type SandboxFile } from '@/lib/execution/e2b'
import { CodeLanguage } from '@/lib/execution/languages'
import {
Expand Down Expand Up @@ -53,7 +53,7 @@ export interface E2BDocFormat {
* pptx/docx → node, pdf/xlsx → python. Only meaningful when the E2B doc sandbox
* is enabled; callers gate on isE2BDocEnabled before using this.
*/
export function getE2BDocFormat(fileName: string): E2BDocFormat | null {
export async function getE2BDocFormat(fileName: string): Promise<E2BDocFormat | null> {
const l = fileName.toLowerCase()
if (l.endsWith('.pptx'))
return {
Expand All @@ -79,10 +79,10 @@ export function getE2BDocFormat(fileName: string): E2BDocFormat | null {
contentType: PDF_MIME,
sourceMime: PYTHON_PDF_SOURCE_MIME,
}
// xlsx is gated behind the mothership beta flag (like plans/changelog): the
// xlsx is gated behind the mothership-beta feature flag (like plans/changelog): the
// skill + prompt are gated on the Go side, and this is the single Sim chokepoint
// that keeps the compile/serve/check/recalc paths off for xlsx when beta is off.
if (l.endsWith('.xlsx') && isMothershipBetaFeaturesEnabled)
if (l.endsWith('.xlsx') && (await isFeatureEnabled('mothership-beta')))
return {
ext: 'xlsx',
engine: 'python',
Expand Down Expand Up @@ -385,7 +385,7 @@ export async function compileDoc(
args: CompileArgs
): Promise<{ buffer: Buffer; contentType: string }> {
const { source, fileName, workspaceId } = args
const fmt = getE2BDocFormat(fileName)
const fmt = await getE2BDocFormat(fileName)
if (!fmt) throw new Error(`Unsupported document format: ${fileName}`)

const existing = await loadCompiledDoc(workspaceId, source, fmt.ext)
Expand All @@ -409,7 +409,7 @@ export async function loadCompiledDocByExt(
source: string,
ext: string
): Promise<{ buffer: Buffer; contentType: string } | null> {
const fmt = getE2BDocFormat(`x.${ext}`)
const fmt = await getE2BDocFormat(`x.${ext}`)
if (!fmt) return null
const buffer = await loadCompiledDoc(workspaceId, source, fmt.ext)
return buffer ? { buffer, contentType: fmt.contentType } : null
Expand Down
2 changes: 1 addition & 1 deletion apps/sim/lib/copilot/tools/server/files/edit-content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ export const editContentServerTool: BaseServerTool<EditContentArgs, EditContentR
try {
const { operation, fileRecord } = intent
const docInfo = getDocumentFormatInfo(fileRecord.name)
const e2bFmt = isE2BDocEnabled ? getE2BDocFormat(fileRecord.name) : null
const e2bFmt = isE2BDocEnabled ? await getE2BDocFormat(fileRecord.name) : null

let finalContent: string
switch (operation) {
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/lib/copilot/tools/server/files/workspace-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -203,13 +203,13 @@ export async function compileDocForWrite(args: {
}): Promise<CompileForWriteResult> {
const { source, fileName, workspaceId, ownerKey, signal, fallbackMime } = args
const docInfo = getDocumentFormatInfo(fileName)
const e2bFmt = isE2BDocEnabled ? getE2BDocFormat(fileName) : null
const e2bFmt = isE2BDocEnabled ? await getE2BDocFormat(fileName) : null

if (!e2bFmt && fileName.toLowerCase().endsWith('.xlsx')) {
return {
ok: false,
message: isE2BDocEnabled
? 'Excel (.xlsx) generation is currently behind a beta flag (MOTHERSHIP_BETA_FEATURES) and is not available.'
? 'Excel (.xlsx) generation is currently behind the mothership-beta feature flag and is not available.'
: 'Excel (.xlsx) generation requires the E2B document sandbox, which is not enabled in this environment.',
}
}
Expand Down
4 changes: 2 additions & 2 deletions apps/sim/lib/copilot/vfs/workflow-alias-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ import {
resolveWorkspacePlanAliasPath,
type WorkflowAliasTarget,
} from '@/lib/copilot/vfs/workflow-aliases'
import { isMothershipBetaFeaturesEnabled } from '@/lib/core/config/env-flags'
import { isFeatureEnabled } from '@/lib/core/config/feature-flags'
import { canonicalizeVfsPath } from './path-utils'

export async function resolveWorkflowAliasForWorkspace(args: {
workspaceId: string
path: string
}): Promise<WorkflowAliasTarget | null> {
if (!isMothershipBetaFeaturesEnabled) return null
if (!(await isFeatureEnabled('mothership-beta'))) return null
Comment thread
cursor[bot] marked this conversation as resolved.
if (!isPlanAliasPath(args.path)) return null

let canonicalPath: string
Expand Down
21 changes: 12 additions & 9 deletions apps/sim/lib/copilot/vfs/workspace-vfs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ import {
workspacePlanBackingPath,
workspacePlansBackingFolderPath,
} from '@/lib/copilot/vfs/workflow-aliases'
import { isE2BDocEnabled, isMothershipBetaFeaturesEnabled } from '@/lib/core/config/env-flags'
import { isE2BDocEnabled } from '@/lib/core/config/env-flags'
import { isFeatureEnabled } from '@/lib/core/config/feature-flags'
import {
getAccessibleEnvCredentials,
getAccessibleOAuthCredentials,
Expand Down Expand Up @@ -379,6 +380,7 @@ function getStaticComponentFiles(): Map<string, string> {
export class WorkspaceVFS {
private files: Map<string, string> = new Map()
private _workspaceId = ''
private _betaEnabled = false

get workspaceId(): string {
return this._workspaceId
Expand All @@ -393,6 +395,7 @@ export class WorkspaceVFS {
const start = Date.now()
this.files = new Map()
this._workspaceId = workspaceId
this._betaEnabled = await isFeatureEnabled('mothership-beta', { userId })

// Per-phase wall-clock, stamped on the span so a slow materialize in a
// trace names its bottleneck instead of showing up as unattributed dead
Expand Down Expand Up @@ -591,7 +594,7 @@ export class WorkspaceVFS {
path: string,
suffix: 'style' | 'compiled-check' | 'compiled' | 'render' | 'extract'
): Promise<WorkspaceFileRecord | null> {
if (!isMothershipBetaFeaturesEnabled && isWorkflowAliasBackingPath(path)) {
if (!this._betaEnabled && isWorkflowAliasBackingPath(path)) {
return null
}
const canonicalMatch = path.match(new RegExp(`^files/(.+)/${suffix}$`))
Expand Down Expand Up @@ -642,7 +645,7 @@ export class WorkspaceVFS {
totalLines: 1,
}
}
if (isE2BDocEnabled && getE2BDocFormat(record.name)) {
if (isE2BDocEnabled && (await getE2BDocFormat(record.name))) {
bin = (
await compileDoc({ source: code, fileName: record.name, workspaceId: this._workspaceId })
).buffer
Expand Down Expand Up @@ -695,7 +698,7 @@ export class WorkspaceVFS {
record = await this.resolveWorkspaceFileForDynamicRead(path, 'compiled')
if (!record) return null
const ext = record.name.split('.').pop()?.toLowerCase() ?? ''
const e2bFmt = isE2BDocEnabled ? getE2BDocFormat(record.name) : null
const e2bFmt = isE2BDocEnabled ? await getE2BDocFormat(record.name) : null
const taskId = BINARY_DOC_TASKS[ext]
if (!e2bFmt && !taskId) return null

Expand Down Expand Up @@ -890,7 +893,7 @@ export class WorkspaceVFS {
record = await this.resolveWorkspaceFileForDynamicRead(path, 'compiled-check')
if (!record) return null
const ext = record.name.split('.').pop()?.toLowerCase() ?? ''
const e2bFmt = isE2BDocEnabled ? getE2BDocFormat(record.name) : null
const e2bFmt = isE2BDocEnabled ? await getE2BDocFormat(record.name) : null
const taskId = BINARY_DOC_TASKS[ext]
const isMermaidFile = ext === 'mmd' || ext === 'mermaid'
if (!e2bFmt && !taskId && !isMermaidFile) return null
Expand Down Expand Up @@ -978,7 +981,7 @@ export class WorkspaceVFS {
.replace(/\/content$/, '')
.replace(/^\/+/, '')

if (!isMothershipBetaFeaturesEnabled && isWorkflowAliasBackingPath(fileReference)) {
if (!this._betaEnabled && isWorkflowAliasBackingPath(fileReference)) {
return null
}
if (fileReference.endsWith('/meta.json') || path.endsWith('/meta.json')) return null
Expand All @@ -988,7 +991,7 @@ export class WorkspaceVFS {
try {
const files = await listWorkspaceFiles(this._workspaceId, {
scope,
includeReservedSystemFiles: isMothershipBetaFeaturesEnabled,
includeReservedSystemFiles: this._betaEnabled,
})
const record = findWorkspaceFileRecord(files, fileReference)
if (!record) return null
Expand Down Expand Up @@ -1021,7 +1024,7 @@ export class WorkspaceVFS {
* Returns a summary for WORKSPACE.md generation.
*/
private async materializeWorkflows(workspaceId: string): Promise<WorkspaceMdData['workflows']> {
const workflowArtifactsEnabled = isMothershipBetaFeaturesEnabled
const workflowArtifactsEnabled = this._betaEnabled
const [workflowRows, folderRows] = await Promise.all([
listWorkflows(workspaceId),
listFolders(workspaceId),
Expand Down Expand Up @@ -1404,7 +1407,7 @@ export class WorkspaceVFS {
*/
private async materializeFiles(workspaceId: string): Promise<WorkspaceMdData['files']> {
try {
const workflowArtifactsEnabled = isMothershipBetaFeaturesEnabled
const workflowArtifactsEnabled = this._betaEnabled
const folders = await listWorkspaceFileFolders(workspaceId, {
includeReservedSystemFolders: true,
})
Expand Down
Loading
Loading