Skip to content

Commit ac55b1f

Browse files
committed
refactor(copilot): remove the plan/changelog VFS artifacts and workflow aliases
These beta surfaces are not a direction we are taking, so they come out rather than staying behind a flag. Gone: the workflow alias modules (path resolution, DB-backed resolver, .plans/.changelogs backing provisioning), the alias materialization in the copilot VFS, the alias write paths in resource-writer and workspace_file, the sandbox alias mounts in function_execute, the reserved backing-path guards across mkdir/mv/create, and the alias resolution in the chat home file picker. xlsx survives but changes owner. It was gated twice across the repo boundary: mothership's xlsx-writing flag gates the skill and prompt, while Sim gated the compile path on mothership-beta. Those live in separate AppConfig applications, so an operator had to flip two flags in two consoles, and off-hosted Sim fell back to the MOTHERSHIP_BETA_FEATURES secret while the mothership half stayed in Sim Cloud's AppConfig — split-brain across an ownership boundary. Mothership controls whether the model ever learns xlsx exists, so if it is never offered it is never requested and the second chokepoint only created a way for the two halves to disagree. Sim's gate is removed; xlsx-writing is now the single owner. With its last consumer gone, the mothership-beta flag and the MOTHERSHIP_BETA_FEATURES secret are deleted. The two entries in the infra repo are harmless until removed separately: they only inject an env var nothing reads, and createEnv runs with skipValidation. The reserved-system-file/folder concept goes with the aliases, since it existed only to hide the backing rows. includeReservedSystemFiles and includeReservedSystemFolders are removed rather than left as options every caller passes true to. backingVfsPath is removed for the same reason — nothing sets it once aliases are gone, so it was an always-undefined field on tool results. Test coverage is preserved rather than deleted with the feature. resource-writer.test.ts looked alias-only but three of its eleven cases cover the generic create path that survives; those are kept and the file retitled. Two open_resource tests and one output-path test used alias-shaped strings while asserting generic behavior; retargeted or dropped where a sibling already covers it.
1 parent b0c2d66 commit ac55b1f

28 files changed

Lines changed: 47 additions & 1835 deletions

apps/sim/app/api/function/execute/route.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1343,7 +1343,6 @@ async function maybeExportSandboxFilesToWorkspace(args: {
13431343
fileId: file.id,
13441344
fileName: file.name,
13451345
vfsPath: file.vfsPath,
1346-
backingVfsPath: file.backingVfsPath,
13471346
downloadUrl: file.downloadUrl,
13481347
sandboxPath: file.sandboxPath,
13491348
size: file.exportedBytes,

apps/sim/app/workspace/[workspaceId]/home/home.tsx

Lines changed: 2 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,6 @@ import { usePostHog } from 'posthog-js/react'
2020
import { requestJson } from '@/lib/api/client/request'
2121
import { createWorkflowContract } from '@/lib/api/contracts'
2222
import { canonicalWorkspaceFilePath } from '@/lib/copilot/vfs/path-utils'
23-
import {
24-
buildWorkflowAliasWorkflowEntries,
25-
resolveWorkflowAliasPath,
26-
resolveWorkspacePlanAliasPath,
27-
} from '@/lib/copilot/vfs/workflow-aliases'
2823
import {
2924
LandingPromptStorage,
3025
type LandingWorkflowSeed,
@@ -379,54 +374,28 @@ export function Home({ chatId, userName, userId }: HomeProps) {
379374
removeResource(resolved.type, resolved.id)
380375
}
381376

382-
const workflowAliasEntries = useMemo(
383-
() =>
384-
buildWorkflowAliasWorkflowEntries(
385-
workflows.map((workflow) => ({
386-
id: workflow.id,
387-
name: workflow.name,
388-
folderId: workflow.folderId ?? null,
389-
})),
390-
folders.map((folder) => ({
391-
folderId: folder.id,
392-
folderName: folder.name,
393-
parentId: folder.parentId ?? null,
394-
}))
395-
),
396-
[folders, workflows]
397-
)
398-
399377
const resolveFileResource = useCallback(
400378
(resource: MothershipResource): MothershipResource => {
401379
if (resource.type !== 'file') return resource
402380

403381
const reference = (resource.path || resource.id).trim()
404-
const workspacePlanAlias = resolveWorkspacePlanAliasPath(reference)
405-
const workflowAlias = workspacePlanAlias
406-
? null
407-
: resolveWorkflowAliasPath(reference, workflowAliasEntries)
408-
const alias = workspacePlanAlias || workflowAlias
409-
const targetPath = alias && alias.kind !== 'plans_dir' ? alias.backingPath : reference
410382

411383
const file = workspaceFiles.find((candidate) => {
412384
const candidatePath = canonicalWorkspaceFilePath({
413385
folderPath: candidate.folderPath,
414386
name: candidate.name,
415387
})
416-
return (
417-
candidate.id === reference || candidatePath === reference || candidatePath === targetPath
418-
)
388+
return candidate.id === reference || candidatePath === reference
419389
})
420390

421391
if (!file) return resource
422392
return {
423393
...resource,
424394
id: file.id,
425395
title: resource.title || file.name,
426-
path: alias ? reference : resource.path,
427396
}
428397
},
429-
[workflowAliasEntries, workspaceFiles]
398+
[workspaceFiles]
430399
)
431400

432401
function handleWorkspaceResourceSelect(resource: MothershipResource) {

apps/sim/lib/copilot/request/tools/files.test.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -93,12 +93,10 @@ describe('serializeOutputForFile (json / txt / md)', () => {
9393
})
9494

9595
describe('normalizeOutputWorkspaceFileName', () => {
96-
it('derives the leaf file name from workflow alias output paths', () => {
97-
expect(normalizeOutputWorkspaceFileName('workflows/My%20Workflow/changelog.md')).toBe(
98-
'changelog.md'
99-
)
96+
it('derives the leaf file name from nested, percent-encoded output paths', () => {
97+
expect(normalizeOutputWorkspaceFileName('files/My%20Folder/notes.md')).toBe('notes.md')
10098
expect(
101-
normalizeOutputWorkspaceFileName('workflows/My%20Workflow/.plans/phase%201/implementation.md')
99+
normalizeOutputWorkspaceFileName('files/My%20Folder/phase%201/implementation.md')
102100
).toBe('implementation.md')
103101
})
104102

apps/sim/lib/copilot/tools/handlers/function-execute.test.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,6 @@ vi.mock('@/lib/copilot/vfs/path-utils', () => ({
6464
decodeVfsPathSegments: (p: string) => p.split('/'),
6565
encodeVfsPathSegments: (s: string[]) => s.join('/'),
6666
}))
67-
vi.mock('@/lib/copilot/vfs/workflow-alias-resolver', () => ({
68-
resolveWorkflowAliasForWorkspace: vi.fn().mockResolvedValue(null),
69-
}))
70-
vi.mock('@/lib/copilot/vfs/workflow-aliases', () => ({
71-
isPlanAliasPath: () => false,
72-
workflowAliasSandboxPath: (p: string) => p,
73-
}))
7467

7568
import { executeFunctionExecute } from '@/lib/copilot/tools/handlers/function-execute'
7669

apps/sim/lib/copilot/tools/handlers/function-execute.ts

Lines changed: 8 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import { createLogger } from '@sim/logger'
22
import { decodeVfsPathSegments, encodeVfsPathSegments } from '@/lib/copilot/vfs/path-utils'
3-
import { resolveWorkflowAliasForWorkspace } from '@/lib/copilot/vfs/workflow-alias-resolver'
4-
import { isPlanAliasPath, workflowAliasSandboxPath } from '@/lib/copilot/vfs/workflow-aliases'
53
import { isFeatureEnabled } from '@/lib/core/config/feature-flags'
64
import { getColumnId } from '@/lib/table/column-keys'
75
import { formatCsvValue, neutralizeCsvFormula, toCsvRow } from '@/lib/table/export-format'
@@ -174,17 +172,14 @@ export async function resolveInputFiles(
174172
): Promise<SandboxFile[]> {
175173
const sandboxFiles: SandboxFile[] = []
176174
const mounted: MountedBytes = { buffered: 0, url: 0 }
177-
const betaEnabled = await isFeatureEnabled('mothership-beta')
178175

179176
if (inputFiles?.length && workspaceId) {
180177
if (inputFiles.length > MAX_MOUNTED_FILES) {
181178
throw new Error(
182179
`Too many input files (${inputFiles.length}). Maximum is ${MAX_MOUNTED_FILES}. Mount fewer files.`
183180
)
184181
}
185-
const allFiles = await listWorkspaceFiles(workspaceId, {
186-
includeReservedSystemFiles: betaEnabled,
187-
})
182+
const allFiles = await listWorkspaceFiles(workspaceId)
188183
for (const fileRef of inputFiles) {
189184
const filePath =
190185
typeof fileRef === 'string'
@@ -193,16 +188,7 @@ export async function resolveInputFiles(
193188
? (fileRef as CanonicalFileInput).path
194189
: undefined
195190
if (!filePath) continue
196-
const alias = await resolveWorkflowAliasForWorkspace({ workspaceId, path: filePath })
197-
if (!alias && isPlanAliasPath(filePath)) {
198-
logger.warn('Unsupported plan alias input file path', { filePath })
199-
continue
200-
}
201-
if (alias?.kind === 'plans_dir') {
202-
logger.warn('Input file is a plan alias directory', { filePath })
203-
continue
204-
}
205-
const record = findWorkspaceFileRecord(allFiles, alias?.backingPath ?? filePath)
191+
const record = findWorkspaceFileRecord(allFiles, filePath)
206192
if (!record) {
207193
if (filePath.startsWith('uploads/')) {
208194
throw new Error(
@@ -217,21 +203,14 @@ export async function resolveInputFiles(
217203
typeof fileRef === 'object' && fileRef !== null
218204
? (fileRef as CanonicalFileInput).sandboxPath
219205
: undefined
220-
const mountPath =
221-
explicitSandboxPath ||
222-
(alias ? workflowAliasSandboxPath(alias.aliasPath) : getSandboxWorkspaceFilePath(record))
206+
const mountPath = explicitSandboxPath || getSandboxWorkspaceFilePath(record)
223207
await pushWorkspaceFileMount(sandboxFiles, record, mountPath, mounted)
224208
}
225209
}
226210

227211
if (inputDirectories?.length && workspaceId) {
228-
const folders = await listWorkspaceFileFolders(workspaceId, {
229-
includeReservedSystemFolders: betaEnabled,
230-
})
231-
const allFiles = await listWorkspaceFiles(workspaceId, {
232-
folders,
233-
includeReservedSystemFiles: betaEnabled,
234-
})
212+
const folders = await listWorkspaceFileFolders(workspaceId)
213+
const allFiles = await listWorkspaceFiles(workspaceId, { folders })
235214
for (const dirRef of inputDirectories) {
236215
const dirPath =
237216
typeof dirRef === 'string'
@@ -240,15 +219,7 @@ export async function resolveInputFiles(
240219
? (dirRef as CanonicalDirectoryInput).path
241220
: undefined
242221
if (!dirPath) continue
243-
const alias = await resolveWorkflowAliasForWorkspace({ workspaceId, path: dirPath })
244-
if (alias && alias.kind !== 'plans_dir') {
245-
throw new Error(`Input directory is a plan alias file, not a directory: ${dirPath}`)
246-
}
247-
if (!alias && isPlanAliasPath(dirPath)) {
248-
throw new Error(`Unsupported plan alias directory: ${dirPath}`)
249-
}
250-
const backingDirPath = alias?.backingPath ?? dirPath
251-
const folderSegments = decodeVfsPathSegments(backingDirPath.replace(/^\/?files\/?/, ''))
222+
const folderSegments = decodeVfsPathSegments(dirPath.replace(/^\/?files\/?/, ''))
252223
const folderDisplayPath = folderSegments.join('/')
253224
const folder = folders.find((candidate) => candidate.path === folderDisplayPath)
254225
if (!folder) {
@@ -259,9 +230,7 @@ export async function resolveInputFiles(
259230
dirRef !== null &&
260231
(dirRef as CanonicalDirectoryInput).sandboxPath
261232
? (dirRef as CanonicalDirectoryInput).sandboxPath!
262-
: alias
263-
? workflowAliasSandboxPath(alias.aliasPath)
264-
: `/home/user/files/${encodeVfsPathSegments(folder.path.split('/'))}`
233+
: `/home/user/files/${encodeVfsPathSegments(folder.path.split('/'))}`
265234
const descendants = allFiles.filter((file) => {
266235
if (!file.folderPath) return false
267236
return file.folderPath === folder.path || file.folderPath.startsWith(`${folder.path}/`)
@@ -300,11 +269,7 @@ export async function resolveInputFiles(
300269
for (const record of descendants) {
301270
const relativeFolder =
302271
record.folderPath?.slice(folder.path.length).replace(/^\/+/, '') ?? ''
303-
const relativePath = alias
304-
? encodeVfsPathSegments(
305-
[relativeFolder, record.name].filter(Boolean).join('/').split('/')
306-
)
307-
: [relativeFolder, record.name].filter(Boolean).join('/')
272+
const relativePath = [relativeFolder, record.name].filter(Boolean).join('/')
308273
await pushWorkspaceFileMount(sandboxFiles, record, `${mountRoot}/${relativePath}`, mounted)
309274
}
310275
}

apps/sim/lib/copilot/tools/handlers/resources.test.ts

Lines changed: 0 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -104,62 +104,4 @@ describe('executeOpenResource', () => {
104104
})
105105
})
106106

107-
it('opens workflow alias file paths through workspace file reference resolution', async () => {
108-
resolveWorkspaceFileReferenceMock.mockResolvedValue({
109-
id: 'wf_plan_file',
110-
name: 'implementation.md',
111-
folderPath: 'system/workflows/My Workflow/.plans',
112-
})
113-
114-
const result = await executeOpenResource(
115-
{
116-
resources: [{ type: 'file', path: 'workflows/My%20Workflow/.plans/implementation.md' }],
117-
},
118-
{ userId: 'user-1', workflowId: 'workflow-1', workspaceId: 'workspace-1' }
119-
)
120-
121-
expect(resolveWorkspaceFileReferenceMock).toHaveBeenCalledWith(
122-
'workspace-1',
123-
'workflows/My%20Workflow/.plans/implementation.md'
124-
)
125-
expect(result).toMatchObject({
126-
success: true,
127-
resources: [
128-
{
129-
type: 'file',
130-
id: 'wf_plan_file',
131-
title: 'implementation.md',
132-
path: 'files/system/workflows/My%20Workflow/.plans/implementation.md',
133-
},
134-
],
135-
})
136-
})
137-
138-
it('opens root plan alias file paths through workspace file reference resolution', async () => {
139-
resolveWorkspaceFileReferenceMock.mockResolvedValue({
140-
id: 'wf_root_plan',
141-
name: 'root.md',
142-
folderPath: 'system/.plans',
143-
})
144-
145-
const result = await executeOpenResource(
146-
{
147-
resources: [{ type: 'file', path: '.plans/root.md' }],
148-
},
149-
{ userId: 'user-1', workflowId: 'workflow-1', workspaceId: 'workspace-1' }
150-
)
151-
152-
expect(resolveWorkspaceFileReferenceMock).toHaveBeenCalledWith('workspace-1', '.plans/root.md')
153-
expect(result).toMatchObject({
154-
success: true,
155-
resources: [
156-
{
157-
type: 'file',
158-
id: 'wf_root_plan',
159-
title: 'root.md',
160-
path: 'files/system/.plans/root.md',
161-
},
162-
],
163-
})
164-
})
165107
})

apps/sim/lib/copilot/tools/handlers/vfs-mutate.test.ts

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -273,15 +273,6 @@ describe('vfs mv/cp', () => {
273273
})
274274
expect(result.success).toBe(true)
275275
})
276-
277-
it('rejects reserved alias backing paths', async () => {
278-
const result = await executeVfsMv(
279-
{ sources: ['files/.plans/wf_1/launch.md'], destination: 'files/launch.md' },
280-
context
281-
)
282-
expect(result.success).toBe(false)
283-
expect(result.error).toContain('Reserved system paths')
284-
})
285276
})
286277

287278
describe('workflows', () => {
@@ -420,14 +411,11 @@ describe('vfs mv/cp', () => {
420411
})
421412
})
422413

423-
it('rejects flat namespaces and reserved paths', async () => {
424-
const result = await executeVfsMkdir({ paths: ['tables/CRM', 'files/.plans/wf_1'] }, context)
414+
it('rejects flat namespaces', async () => {
415+
const result = await executeVfsMkdir({ paths: ['tables/CRM'] }, context)
425416
expect(result.success).toBe(false)
426417
expect(result.output).toMatchObject({
427-
results: [
428-
{ from: 'tables/CRM', error: expect.stringContaining('flat namespace') },
429-
{ from: 'files/.plans/wf_1', error: expect.stringContaining('Reserved') },
430-
],
418+
results: [{ from: 'tables/CRM', error: expect.stringContaining('flat namespace') }],
431419
})
432420
expect(mocks.ensureWorkspaceFileFolderPath).not.toHaveBeenCalled()
433421
})

apps/sim/lib/copilot/tools/handlers/vfs-mutate.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {
1616
decodeVfsPathSegments,
1717
encodeVfsPathSegments,
1818
} from '@/lib/copilot/vfs/path-utils'
19-
import { isWorkflowAliasBackingPath } from '@/lib/copilot/vfs/workflow-aliases'
2019
import { generateRequestId } from '@/lib/core/utils/request'
2120
import { getKnowledgeBases, updateKnowledgeBase } from '@/lib/knowledge/service'
2221
import { listTables, renameTable } from '@/lib/table/service'
@@ -161,11 +160,6 @@ export async function executeVfsMkdir(
161160
outcomes.push({ from: path, kind, error: 'Path must include at least one folder segment' })
162161
continue
163162
}
164-
if (top === 'files' && isWorkflowAliasBackingPath(path)) {
165-
outcomes.push({ from: path, kind, error: `Reserved system path: ${path}` })
166-
continue
167-
}
168-
169163
try {
170164
assertMutationNotAborted(context)
171165
let folderId: string | null
@@ -348,15 +342,6 @@ async function mutateWorkspaceFiles(
348342
error: 'Workspace files cannot be copied — cp only duplicates workflows.',
349343
}
350344
}
351-
for (const path of [...sources, destination]) {
352-
if (isWorkflowAliasBackingPath(path)) {
353-
return {
354-
success: false,
355-
error: `Reserved system paths cannot be moved or renamed: ${path}`,
356-
}
357-
}
358-
}
359-
360345
const dest = await planDestination({
361346
destination,
362347
sourceCount: sources.length,

apps/sim/lib/copilot/tools/server/files/create-file.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
type ServerToolContext,
77
} from '@/lib/copilot/tools/server/base-tool'
88
import { writeWorkspaceFileByPath } from '@/lib/copilot/vfs/resource-writer'
9-
import { isPlanAliasPath } from '@/lib/copilot/vfs/workflow-aliases'
109
import { inferContentType } from './workspace-file'
1110

1211
const logger = createLogger('CreateFileServerTool')
@@ -27,7 +26,6 @@ interface CreateFileResult {
2726
name: string
2827
contentType: string
2928
vfsPath: string
30-
backingVfsPath?: string
3129
}
3230
}
3331

@@ -52,13 +50,6 @@ export const createFileServerTool: BaseServerTool<CreateFileArgs, CreateFileResu
5250
}
5351
const outputPath =
5452
outputFile?.path ?? (fileName.startsWith('files/') ? fileName : `files/${fileName}`)
55-
if (isPlanAliasPath(outputPath)) {
56-
return {
57-
success: false,
58-
message:
59-
'create_file does not initialize plan aliases; changelog.md is created automatically per workflow.',
60-
}
61-
}
6253
const contentType = outputFile?.mimeType ?? inferContentType(outputPath, explicitType)
6354
const emptyBuffer = Buffer.from('', 'utf-8')
6455

@@ -90,7 +81,6 @@ export const createFileServerTool: BaseServerTool<CreateFileArgs, CreateFileResu
9081
name: result.name,
9182
contentType,
9283
vfsPath: result.vfsPath,
93-
backingVfsPath: result.backingVfsPath,
9484
},
9585
}
9686
},

0 commit comments

Comments
 (0)