Skip to content

Commit f254678

Browse files
committed
fix open resource
1 parent ffe56fc commit f254678

6 files changed

Lines changed: 78 additions & 29 deletions

File tree

apps/sim/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-content/resource-content.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ function EmbeddedFileActions({ workspaceId, fileId }: EmbeddedFileActionsProps)
260260
}, [file])
261261

262262
const handleOpenInFiles = useCallback(() => {
263-
router.push(`/workspace/${workspaceId}/files?fileId=${fileId}`)
263+
router.push(`/workspace/${workspaceId}/files?fileId=${encodeURIComponent(fileId)}`)
264264
}, [router, workspaceId, fileId])
265265

266266
return (

apps/sim/lib/copilot/orchestrator/tool-executor/index.ts

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { validateMcpDomain } from '@/lib/mcp/domain-check'
1616
import { mcpService } from '@/lib/mcp/service'
1717
import { generateMcpServerId } from '@/lib/mcp/utils'
1818
import { getAllOAuthServices } from '@/lib/oauth/utils'
19+
import { getWorkspaceFile } from '@/lib/uploads/contexts/workspace/workspace-file-manager'
1920
import {
2021
deleteCustomTool,
2122
getCustomToolById,
@@ -24,7 +25,7 @@ import {
2425
} from '@/lib/workflows/custom-tools/operations'
2526
import { deleteSkill, listSkills, upsertSkills } from '@/lib/workflows/skills/operations'
2627
import { getWorkflowById } from '@/lib/workflows/utils'
27-
import { isMcpTool } from '@/executor/constants'
28+
import { isMcpTool, isUuid } from '@/executor/constants'
2829
import { executeTool } from '@/tools'
2930
import { getTool, resolveToolId } from '@/tools/utils'
3031
import {
@@ -1029,15 +1030,42 @@ const SIM_WORKFLOW_TOOL_HANDLERS: Record<
10291030
list: (p, c) => executeVfsList(p, c),
10301031

10311032
// Resource visibility
1032-
open_resource: async (p: OpenResourceParams) => {
1033+
open_resource: async (p: OpenResourceParams, c: ExecutionContext) => {
10331034
const validated = validateOpenResourceParams(p)
10341035
if (!validated.success) {
10351036
return { success: false, error: validated.error }
10361037
}
10371038

10381039
const params = validated.params
10391040
const resourceType = params.type
1040-
const resourceId = params.id
1041+
let resourceId = params.id
1042+
let title: string = resourceType
1043+
1044+
if (resourceType === 'file') {
1045+
if (!c.workspaceId) {
1046+
return {
1047+
success: false,
1048+
error:
1049+
'Opening a workspace file requires workspace context. Pass the file UUID from files/<name>/meta.json.',
1050+
}
1051+
}
1052+
if (!isUuid(params.id)) {
1053+
return {
1054+
success: false,
1055+
error:
1056+
'open_resource for files requires the canonical UUID from files/<name>/meta.json (the "id" field). Do not pass VFS paths, display names, or file_<name> strings.',
1057+
}
1058+
}
1059+
const record = await getWorkspaceFile(c.workspaceId, params.id)
1060+
if (!record) {
1061+
return {
1062+
success: false,
1063+
error: `No workspace file with id "${params.id}". Confirm the UUID from meta.json.`,
1064+
}
1065+
}
1066+
resourceId = record.id
1067+
title = record.name
1068+
}
10411069

10421070
return {
10431071
success: true,
@@ -1046,7 +1074,7 @@ const SIM_WORKFLOW_TOOL_HANDLERS: Record<
10461074
{
10471075
type: resourceType as 'workflow' | 'table' | 'knowledgebase' | 'file',
10481076
id: resourceId,
1049-
title: resourceType,
1077+
title,
10501078
},
10511079
],
10521080
}

apps/sim/lib/copilot/vfs/operations.test.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,16 @@ describe('grep', () => {
8181
expect(lineOnly).toHaveLength(0)
8282
})
8383

84+
it('treats trailing slash on directory scope like grep (files/ matches files/foo)', () => {
85+
const files = vfsFromEntries([
86+
['files/TEST BOY.md/meta.json', '"name": "TEST BOY.md"'],
87+
['workflows/x', 'TEST BOY'],
88+
])
89+
const hits = grep(files, 'TEST BOY', 'files/', { outputMode: 'files_with_matches' })
90+
expect(hits).toContain('files/TEST BOY.md/meta.json')
91+
expect(hits).not.toContain('workflows/x')
92+
})
93+
8494
it('scopes to directory prefix without matching unrelated prefixes', () => {
8595
const files = vfsFromEntries([
8696
['workflows/a/x', 'needle'],

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,19 @@ function splitLinesForGrep(content: string): string[] {
5757
* Returns true when `filePath` is `scope` or a descendant path (`scope/...`). If `scope` contains
5858
* `*` or `?`, filters with micromatch `isMatch` and {@link VFS_GLOB_OPTIONS}. Other characters
5959
* (including `[`, `{`, spaces) use directory-prefix logic so literal VFS path segments are not
60-
* parsed as glob syntax.
60+
* parsed as glob syntax. Trailing slashes are stripped so `files/` and `files` both scope under
61+
* `files/...`.
6162
*/
6263
function pathWithinGrepScope(filePath: string, scope: string): boolean {
6364
const scopeUsesStarOrQuestionGlob = /[*?]/.test(scope)
6465
if (scopeUsesStarOrQuestionGlob) {
6566
return micromatch.isMatch(filePath, scope, VFS_GLOB_OPTIONS)
6667
}
67-
return filePath === scope || filePath.startsWith(scope + '/')
68+
const base = scope.replace(/\/+$/, '')
69+
if (base === '') {
70+
return true
71+
}
72+
return filePath === base || filePath.startsWith(`${base}/`)
6873
}
6974

7075
/**

apps/sim/lib/uploads/contexts/workspace/workspace-file-manager.ts

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import {
1313
incrementStorageUsage,
1414
} from '@/lib/billing/storage'
1515
import { normalizeVfsSegment } from '@/lib/copilot/vfs/normalize-segment'
16+
import { normalizeWorkspaceFileReference } from '@/lib/uploads/contexts/workspace/workspace-file-reference'
17+
18+
export { normalizeWorkspaceFileReference }
1619
import {
1720
downloadFile,
1821
hasCloudStorage,
@@ -331,30 +334,9 @@ export async function listWorkspaceFiles(
331334
}
332335
}
333336

334-
/**
335-
* Normalize a workspace file reference to its display name.
336-
* Supports raw names and VFS-style paths like `files/name`, `files/name/content`,
337-
* and `files/name/meta.json`.
338-
*/
339-
export function normalizeWorkspaceFileReference(fileReference: string): string {
340-
const trimmed = fileReference.trim().replace(/^\/+/, '')
341-
342-
if (trimmed.startsWith('files/')) {
343-
const withoutPrefix = trimmed.slice('files/'.length)
344-
if (withoutPrefix.endsWith('/meta.json')) {
345-
return withoutPrefix.slice(0, -'/meta.json'.length)
346-
}
347-
if (withoutPrefix.endsWith('/content')) {
348-
return withoutPrefix.slice(0, -'/content'.length)
349-
}
350-
return withoutPrefix
351-
}
352-
353-
return trimmed
354-
}
355-
356337
/**
357338
* Find a workspace file record in an existing list from either its id or a VFS/name reference.
339+
* For copilot `open_resource` and the resource panel, use {@link getWorkspaceFile} with a UUID only.
358340
*/
359341
export function findWorkspaceFileRecord(
360342
files: WorkspaceFileRecord[],
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
/**
2+
* Normalize a workspace file reference to its display name.
3+
* Supports raw names and VFS-style paths like `files/name`, `files/name/content`,
4+
* and `files/name/meta.json`.
5+
*
6+
* Used by storage resolution (`findWorkspaceFileRecord`), not by `open_resource`, which
7+
* requires the canonical database UUID only.
8+
*/
9+
export function normalizeWorkspaceFileReference(fileReference: string): string {
10+
const trimmed = fileReference.trim().replace(/^\/+/, '')
11+
12+
if (trimmed.startsWith('files/')) {
13+
const withoutPrefix = trimmed.slice('files/'.length)
14+
if (withoutPrefix.endsWith('/meta.json')) {
15+
return withoutPrefix.slice(0, -'/meta.json'.length)
16+
}
17+
if (withoutPrefix.endsWith('/content')) {
18+
return withoutPrefix.slice(0, -'/content'.length)
19+
}
20+
return withoutPrefix
21+
}
22+
23+
return trimmed
24+
}

0 commit comments

Comments
 (0)